package org.hotswap.agent.plugin.owb.command;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.spi.Context;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.InjectionTargetFactory;
import org.apache.webbeans.component.BeanAttributesImpl;
import org.apache.webbeans.component.InjectionTargetBean;
import org.apache.webbeans.component.creation.BeanAttributesBuilder;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.container.InjectableBeanManager;
import org.apache.webbeans.container.InjectionTargetFactoryImpl;
import org.apache.webbeans.portable.AnnotatedElementFactory;
import org.apache.webbeans.spi.ContextsService;
import org.apache.webbeans.web.context.WebContextsService;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.logging.AgentLogger.Level;
import org.hotswap.agent.plugin.owb.BeanReloadStrategy;
import org.hotswap.agent.plugin.owb.OwbClassSignatureHelper;
import org.hotswap.agent.plugin.owb.WebBeansContextsServiceTransformer;
import org.hotswap.agent.plugin.owb.beans.ContextualReloadHelper;
import org.hotswap.agent.util.ReflectionHelper;
/**
* Handles creating and redefinition of bean classes in BeanArchive
*
* @author Vladimir Dvorak
*/
public class BeanClassRefreshAgent {
private static AgentLogger LOGGER = AgentLogger.getLogger(BeanClassRefreshAgent.class);
/** True for UnitTests */
public static boolean isTestEnvironment = false;
/**
* Flag to check the reload status. In unit test we need to wait for reload
* finishing before the test can continue. Set flag to true in the test class
* and wait until the flag is false again.
*/
public static boolean reloadFlag = false;
/**
* Called by a reflection command from BeanRefreshCommand transformer.
*
* @param appClassLoader the application class loader
* @param archivePath the archive path
* @param beanClassName the bean class name
* @param oldSignatureByStrategy the old signature by strategy
* @param strReloadStrategy the bean reload strategy
* @throws IOException error working with classDefinition
*/
public static void reloadBean(ClassLoader appClassLoader, String beanClassName, String oldSignatureByStrategy, String strReloadStrategy) throws IOException {
try {
BeanReloadStrategy reloadStrategy;
try {
reloadStrategy = BeanReloadStrategy.valueOf(strReloadStrategy);
} catch (Exception e) {
reloadStrategy = BeanReloadStrategy.NEVER;
}
Class<?> beanClass = appClassLoader.loadClass(beanClassName);
doReloadBean(appClassLoader, beanClass, oldSignatureByStrategy, reloadStrategy);
} catch (ClassNotFoundException e) {
LOGGER.error("Bean class not found.", e);
} finally {
reloadFlag = false;
}
}
/**
* Reload bean in existing bean manager.
*
* @param appClassLoader the class loader
* @param beanClass the bean class
* @param oldSignatureByStrategy the old signature by strategy
* @param reloadStrategy the reload strategy
*/
@SuppressWarnings("rawtypes")
private static void doReloadBean(ClassLoader appClassLoader, Class<?> beanClass, String oldSignatureByStrategy, BeanReloadStrategy reloadStrategy) {
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(appClassLoader);
// check if it is Object descendant
if (Object.class.isAssignableFrom(beanClass)) {
BeanManagerImpl beanManager = null;
BeanManager bm = CDI.current().getBeanManager();
if (bm instanceof BeanManagerImpl) {
beanManager = (BeanManagerImpl) bm;
} else if (bm instanceof InjectableBeanManager){
beanManager = (BeanManagerImpl) ReflectionHelper.get(bm, "bm");
}
Set<Bean<?>> beans = beanManager.getBeans(beanClass);
if (beans != null && !beans.isEmpty()) {
for (Bean<?> bean : beans) {
// just now only managed beans
if (bean instanceof InjectionTargetBean) {
doReloadInjectionTargetBean(beanManager, beanClass, (InjectionTargetBean) bean, oldSignatureByStrategy, reloadStrategy);
} else {
LOGGER.warning("reloadBean() : class '{}' reloading is not implemented ({}).",
bean.getClass().getName(), bean.getBeanClass());
}
}
LOGGER.debug("Bean reloaded '{}'", beanClass.getName());
} else {
// Create new bean
HaBeanDeployer.doDefineManagedBean(beanManager, beanClass);
}
}
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void doReloadInjectionTargetBean(BeanManagerImpl beanManager, Class<?> beanClass, InjectionTargetBean bean,
String oldSignatureByStrategy, BeanReloadStrategy reloadStrategy) {
createAnnotatedTypeForExistingBeanClass(beanManager, beanClass, bean);
String signatureByStrategy = OwbClassSignatureHelper.getSignatureByStrategy(reloadStrategy, beanClass);
if (reloadStrategy == BeanReloadStrategy.CLASS_CHANGE ||
(reloadStrategy != BeanReloadStrategy.NEVER && signatureByStrategy != null && !signatureByStrategy.equals(oldSignatureByStrategy))) {
// Reload bean in contexts - invalidates existing instances
doReloadInjectionTargetBeanInContexts(beanManager, beanClass, bean);
} else {
// keep beans in contexts, reinitialize bean injection points
try {
WebBeansContext wbc = beanManager.getWebBeansContext();
ContextsService contextsService = wbc.getContextsService();
if (!isTestEnvironment && contextsService instanceof WebContextsService) {
// For WebContextService(web application) iterate over all combination of context
// WebContextsTracker can't be directly used here, since it can be in different class loaders (Tomee)
// so we can use inner Iterator as workaround
Object ctxTracker = ReflectionHelper.get(contextsService, WebBeansContextsServiceTransformer.CONTEXT_TRACKER_FLD_NAME);
if (ctxTracker != null) {
try {
// iterate over contexts combination
Iterator it = ((Iterable ) ctxTracker).iterator();
while (it.hasNext()) {
it.next();
Object get = beanManager.getContext(bean.getScope()).get(bean);
if (get != null) {
LOGGER.debug("Bean injection points are reinitialized '{}'", beanClass.getName());
bean.getProducer().inject(get, beanManager.createCreationalContext(bean));
}
}
} finally {
contextsService.removeThreadLocals();
}
} else {
LOGGER.error("ContextTracker not found on class '{}'", contextsService.getClass().getName());
}
} else {
// For DefaultContextdService and testEnviroment use current contexts
Object get = beanManager.getContext(bean.getScope()).get(bean);
if (get != null) {
LOGGER.debug("Bean injection points are reinitialized '{}'", beanClass.getName());
bean.getProducer().inject(get, beanManager.createCreationalContext(bean));
}
}
} catch (javax.enterprise.context.ContextNotActiveException e) {
LOGGER.warning("No active contexts for {}", beanClass.getName());
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void createAnnotatedTypeForExistingBeanClass(BeanManagerImpl beanManager, Class<?> beanClass, InjectionTargetBean bean) {
WebBeansContext wbc = beanManager.getWebBeansContext();
AnnotatedElementFactory annotatedElementFactory = wbc.getAnnotatedElementFactory();
// Clear AnnotatedElementFactory caches
annotatedElementFactory.clear();
AnnotatedType annotatedType = annotatedElementFactory.newAnnotatedType(beanClass);
ReflectionHelper.set(bean, InjectionTargetBean.class, "annotatedType", annotatedType);
// Updated members that were set by bean attributes
BeanAttributesImpl attributes = BeanAttributesBuilder.forContext(wbc).newBeanAttibutes(annotatedType).build();
ReflectionHelper.set(bean, BeanAttributesImpl.class, "types", attributes.getTypes());
ReflectionHelper.set(bean, BeanAttributesImpl.class, "qualifiers", attributes.getQualifiers());
ReflectionHelper.set(bean, BeanAttributesImpl.class, "scope", attributes.getScope());
ReflectionHelper.set(bean, BeanAttributesImpl.class, "name", attributes.getName());
ReflectionHelper.set(bean, BeanAttributesImpl.class, "stereotypes", attributes.getStereotypes());
ReflectionHelper.set(bean, BeanAttributesImpl.class, "alternative", attributes.isAlternative());
InjectionTargetFactory factory = new InjectionTargetFactoryImpl(annotatedType, bean.getWebBeansContext());
InjectionTarget injectionTarget = factory.createInjectionTarget(bean);
ReflectionHelper.set(bean, InjectionTargetBean.class, "injectionTarget", injectionTarget);
LOGGER.debug("New annotated type created for beanClass {}", beanClass.getName());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void doReloadInjectionTargetBeanInContexts(BeanManagerImpl beanManager, Class<?> beanClass, InjectionTargetBean bean) {
try {
Map<Class<? extends Annotation>, List<Context>> allContexts = getContexts(beanManager);
List<Context> ctxList = allContexts.get(bean.getScope());
if(ctxList != null) {
for(Context context: ctxList) {
if (context != null) {
LOGGER.debug("Inspecting context '{}' for bean class {}", context.getClass(), bean.getScope());
if(ContextualReloadHelper.addToReloadSet(context, bean)) {
LOGGER.debug("Bean {}, added to reload set in context {}", bean, context.getClass());
} else {
// try to reinitialize injection points instead...
try {
Object get = context.get(bean);
if (get != null) {
LOGGER.debug("Bean injection points are reinitialized '{}'", beanClass.getName());
bean.getProducer().inject(get, beanManager.createCreationalContext(bean));
}
} catch (Exception e) {
if(LOGGER.isLevelEnabled(Level.DEBUG)) {
LOGGER.debug("Context {} not active for bean: {} in scope: {}",e, context.getClass(), beanClass.getName(), bean.getScope());
} else {
LOGGER.warning("Context {} not active for bean: {} in scope: {}", context.getClass(), beanClass.getName(), bean.getScope());
}
}
}
} else {
LOGGER.debug("No active contexts for bean: {} in scope: {}", bean.getScope(), beanClass.getName());
}
}
} else {
LOGGER.debug("No active contexts for bean: {} in scope: {}", bean.getScope(), beanClass.getName());
}
} catch (Exception e) {
LOGGER.warning("Context for {} failed to reload", e, beanClass.getName());
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<Class<? extends Annotation>, List<Context>> getContexts(BeanManagerImpl beanManagerImpl){
try {
Field contextsField = BeanManagerImpl.class.getField("contextMap");
contextsField.setAccessible(true);
Map<Class<? extends Annotation>, List<Context>> ctxs= (Map) contextsField.get(beanManagerImpl);
return ctxs;
} catch (IllegalAccessException |IllegalArgumentException | NoSuchFieldException | SecurityException e) {
LOGGER.warning("BeanManagerImpl.contexts not accessible", e);
}
return Collections.emptyMap();
}
}